package com.cpr.demo.config;

import com.cpr.demo.controller.LogTestController;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author Chen Panrui
 * created at 2021/8/31 16:18
 *
 * bean:executorService的类型是：Executor -> ExecutorService -> ThreadPoolExecutor[JDK的原生类]
 * bean:executorWrappedMDC的类型是：Executor -> ThreadPoolTaskExecutor[spring的包装类]
 */
@Configuration
@Slf4j
public class ThreadPoolConfig {

    @Bean("executorService")
    @Primary
    public Executor configThreadPool(){
        log.info(">>>初始化-普通的ThreadPool...");
        return new ThreadPoolExecutor(5,
                10,
                600L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10),
                new ThreadPoolExecutor.AbortPolicy());
    }

    /**
     * 统一处理MDC打印requestId
     * 测试executor可以打印rid例子 {@link LogTestController#log1()}
     *
     * ThreadLocal工作方式 {@link com.cpr.demo.thread.ThreadLocalTest#testThreadLocal() }
     *
     * ThreadLocal引发内存泄露的原因：
     * Thread类里面的定义成员ThreadLocal.ThreadLocalMap threadLocals; 中键的实例即ThreadLocal实例的生命周期 是跟着线程池的worker线程，
     * 换言之，如果worker线程一直不会被回收，并且
     * worker线程的 ThreadLocal.ThreadLocalMap threadLocals 一直再set新的ThreadLocal的实例，而不remove掉就会引起OOM
     * @return executor
     */
    @Bean("executorWrappedMDC")
    public Executor configMdcThreadPool(){
        log.info(">>>初始化-增加父子线程复制MDC功能的ThreadPool...");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(){
            @Override
            public void execute(Runnable task) {
                super.execute(new Runnable() {
                    // 注意: 该map的引用 已经和 LogInterceptor.preHandler()里面的{MDC.put("rid", UUID.randomUUID().toString());}这儿的map不是同一个，是一个拥有相同值Map<'rid','UUID'>的副本
                    Map<String, String> map = MDC.getCopyOfContextMap();
                    @Override
                    public void run() {
                        MDC.setContextMap(map);
                        try {
                            task.run();
                        } finally {
                            MDC.clear();
                        }
                    }
                });
            }

            @Override
            public Future<?> submit(Runnable task) {
                Map<String, String> map = MDC.getCopyOfContextMap();
                return super.submit(()->{
                    MDC.setContextMap(map);
                    try {
                        task.run();
                    } finally {
                        MDC.clear();
                    }
                });
            }
        };
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setKeepAliveSeconds(600);
        executor.setQueueCapacity(10);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }

}
